/** <!--------------------------------------------------------------------------------------------->
 * @file SvnConnection.cpp
 *
 * GSvn - A Graphical Client for SVN
 * Copyright (C) 2012  Karl Krach, gsvn@bluespirit.la
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 <!---------------------------------------------------------------------------------------------> */


// -- includes --
#include "SvnConnection.h"
#include <subversion-1/svn_cmdline.h>
#include <subversion-1/svn_pools.h>
#include <subversion-1/svn_fs.h>
#include <subversion-1/svn_client.h>
#include <subversion-1/svn_mergeinfo.h>
#include <subversion-1/svn_repos.h>
#include <apr_tables.h>
#include <apr_strings.h>
#include <apr_general.h>
#include <apr_signal.h>
#include <QDebug>
#include <QUrl>
#include "SvnHistoryListener.h"

#define DEBUG_SVN

/** <!--------------------------------------------------------------------------------------------->
 * Creates the SvnConnection object
 *
 * @param pParent pointer to parent object
 <!---------------------------------------------------------------------------------------------> */
SvnConnection::SvnConnection( QObject* pParent ) : QObject( pParent ), m_pPool( NULL ),
    m_pContext( NULL ), m_bInitialized( false ), m_bCanceled( true )
{
}	// END SvnConnection( QWidget* )

/** <!--------------------------------------------------------------------------------------------->
 * Deletes the SvnConnection object
 <!---------------------------------------------------------------------------------------------> */
SvnConnection::~SvnConnection()
{
	if( m_pPool != NULL )
	{
		svn_pool_destroy( m_pPool );
		m_pPool = NULL;
	}

}	// END ~SvnConnection()

/** <!--------------------------------------------------------------------------------------------->
 * Returns true, if the linked library (lib_xxx.so) is compatible to the compiled library (lib_xx.a)
 *
 * @returns true, if the linked library is compatible to the compiled library
 <!---------------------------------------------------------------------------------------------> */
bool SvnConnection::IsLibraryCompatible()
{
	static const svn_version_checklist_t aChecklist[] =
	{
		{ "svn_subr",   svn_subr_version },
		{ "svn_client", svn_client_version },
		{ "svn_wc",     svn_wc_version },
		{ "svn_ra",     svn_ra_version },
		{ "svn_delta",  svn_delta_version },
		{ "svn_diff",   svn_diff_version },
		{ NULL, NULL }
	};

	SVN_VERSION_DEFINE( oLibVersion );
	svn_error_t* pError = svn_ver_check_list( &oLibVersion, aChecklist );

	//
	// Return true, when there was no error
	//
	if( pError == NULL ) return true;

	//
	// On error print warning and return false!
	//
	qWarning() << "Version mismatch:" << pError->message;
	svn_error_clear( pError );

	return false;

}	// END IsLibraryCompatible()

/** <!--------------------------------------------------------------------------------------------->
 * Returns the version of the SVN library, which was linked to the application
 *
 * @returns the version of the SVN library, which was linked to the application
 <!---------------------------------------------------------------------------------------------> */
QString SvnConnection::GetLinkedLibVersion()
{
	SVN_VERSION_DEFINE( oLibVersion );

	return QString("%1.%1-%3%4").arg(oLibVersion.major).arg(oLibVersion.minor)
	                            .arg(oLibVersion.patch).arg(oLibVersion.tag);

}	// END GetLinkedLibVersion()

/** <!--------------------------------------------------------------------------------------------->
 * Returns the version of the SVN library, which is loaded to the application at runtime
 *
 * @returns the version of the SVN library, which is loaded to the application at runtime
 <!---------------------------------------------------------------------------------------------> */
QString SvnConnection::GetLoadedLibVersion()
{
	const svn_version_t* pLibVersion = svn_subr_version();

	const svn_version_t oLibVersion = *pLibVersion;
	delete pLibVersion;

	return QString("%1.%1-%3%4").arg(oLibVersion.major).arg(oLibVersion.minor)
	                            .arg(oLibVersion.patch).arg(oLibVersion.tag);

}	// END GetLoadedLibVersion()

/** <!--------------------------------------------------------------------------------------------->
 * Initializes the SVN library
 *
 * @returns false, if the initilization failed
 <!---------------------------------------------------------------------------------------------> */
bool SvnConnection::InitializeSvnLibrary()
{
	svn_error_t* pError;
	Q_ASSERT( m_pPool == NULL );
	Q_ASSERT( m_pContext == NULL );

	//
	// Initializes the Apache Portable Runtime (APR), sets the locale and registers stderr for
	// the error reporting
	//
	// FIXME: Replace this by non-cmdline methods!
	//
	if( svn_cmdline_init("gsvn", stderr) != EXIT_SUCCESS )
	{
		qWarning() << "Error in svn_cmdline_init!";
		return false;
	}

	// Creates a root-pool with the default allocator
	m_pPool = svn_pool_create( NULL );


	//
	// Initialize the FileSystem library
	//
	if( (pError = svn_fs_initialize( m_pPool )) != NULL )
	{
		qWarning() << "Error while svn_fs_initialize: " << pError->message;

		svn_error_clear( pError );
		return false;
	}

	//
	// Initialize the Repository Access library
	//
	if( (pError = svn_ra_initialize( m_pPool )) != NULL )
	{
		qWarning() << "Error in svn_ra_initialize: " << pError->message;

		svn_error_clear( pError );
		return false;
	}

	//
	// Tests for the ~/.subversion directory and creates it otherwise with default values
	//
	if( (pError = svn_config_ensure( NULL , m_pPool )) != NULL )
	{
		qWarning() << "Error in svn_config_ensure: " << pError->message;

		svn_error_clear( pError );
		return false;
	}

#ifdef SIGPIPE
	// Disable SIGPIPE generation for the platforms that have it.
	apr_signal( SIGPIPE, SIG_IGN );
#endif

#ifdef SIGXFSZ
	// Disable SIGXFSZ generation for the platforms that have it, otherwise
	// working with large files when compiled against an APR that doesn't have
	// large file support will crash the program, which is uncool.
	apr_signal( SIGXFSZ, SIG_IGN );
#endif

	m_bInitialized = true;
	return true;

}	// END InitializeSvnLibrary()

/** <!--------------------------------------------------------------------------------------------->
 * Setups the client context
 *
 * @returns false, if the setup failed
 <!---------------------------------------------------------------------------------------------> */
bool SvnConnection::SetupClientContext()
{
	svn_error_t* pError;
	Q_ASSERT( m_pPool != NULL );
	Q_ASSERT( m_pContext == NULL );
	Q_ASSERT( m_bInitialized == true );

	if( (pError = svn_client_create_context(&m_pContext, m_pPool)) != NULL )
	{
		qWarning() << "Error in svn_client_create_context!";

		svn_error_clear( pError );
		return false;
	}

	//
	// Read configuration information from the standard sources and merge it into the config hash
	//
	pError = svn_config_get_config( &(m_pContext->config), NULL, m_pPool);
	if( pError )
	{
		// Fallback to default config if the config directory isn't readable or is not a directory.
		if( APR_STATUS_IS_EACCES(pError->apr_err) || APR_STATUS_IS_ENOTDIR(pError->apr_err) )
		{
			qWarning() << "Warning: " << pError->message;
			qWarning() << "Warning: Using default config instead!";

			svn_error_clear(pError);
		}
		else
		{
			qWarning() << "Error in svn_config_get_config: " << pError->message;

			svn_error_clear( pError );
			return false;
		}
	}
	m_pContext->cancel_func  = SvnConnection::CheckForCancelCallback;
	m_pContext->cancel_baton = (void*) this;
	m_bCanceled = false;

	return true;

}	// END SetupClientContext()

/** <!--------------------------------------------------------------------------------------------->
 * Make the client_ctx capable of authentication users
 *
 * @returns false, if the authentication failed
 <!---------------------------------------------------------------------------------------------> */
bool SvnConnection::Authenticate()
{
	Q_ASSERT( m_pPool != NULL );
	Q_ASSERT( m_pContext != NULL );

	const int iRetryLimit = 2;

	// There are many different kinds of authentication back-end
	// "providers".  See svn_auth.h for a full overview.
	apr_array_header_t* aProviders = apr_array_make( m_pPool, 4,
	                                                 sizeof(svn_auth_provider_object_t*) );
	svn_auth_provider_object_t* pProvider;

	//
	// Receive AuthProvider which promts the user for the Login data (username and password)
	//
	svn_auth_get_simple_prompt_provider( &pProvider,            /* Call by reference */
	                                     PromtUserForLoginData, /* Callback function */
	                                     (void*)this,           /* Baton */
	                                     iRetryLimit,
	                                     m_pPool
	                                    );
	APR_ARRAY_PUSH( aProviders, svn_auth_provider_object_t* ) = pProvider;

	//
	// Receive AuthProvider which promts the user for the username
	//
	svn_auth_get_username_prompt_provider( &pProvider,           /* Call by reference */
	                                       PromtUserForUsername, /* Callback function */
	                                       (void*)this,          /* Baton */
	                                       iRetryLimit,
	                                       m_pPool
	                                      );
	APR_ARRAY_PUSH( aProviders, svn_auth_provider_object_t* ) = pProvider;

	//
	// Register the auth-providers into the context's auth_baton.
	//
	svn_auth_open( &m_pContext->auth_baton, aProviders, m_pPool );

	return true;

}	// END Authenticate()


/** <!--------------------------------------------------------------------------------------------->
 * Requests the history data from the SVN server and returns it to the listner object.
 *
 * @param rNodeIdentifier (in) node, of which the history should be received
 * @param pListener       (in) pointer to object, which receives the history data
 * @returns false on error
 <!---------------------------------------------------------------------------------------------> */
bool SvnConnection::GetCompleteHistory( const SvnRevisionIdentifier& rNodeIdentifier,
                                        SvnHistoryListener* pListener )
{
//	URL_OR_PATH = "https://gsvn.googlecode.com/svn";
//	URL_OR_PATH = "svn://localhost/testproject/branches/release-1.x";
//	URL_OR_PATH = "svn://localhost/testproject/trunk/hugo.txt";
//	URL_OR_PATH = "svn://localhost/testproject/branches/";
//	URL_OR_PATH = "svn://localhost/testproject/branches/work_route/hugo.txt";

	Q_ASSERT( m_bInitialized == true );
	Q_ASSERT( pListener != NULL );

	//
	// Creating pool for local variables (delete it on exit!)
	//
	apr_pool_t *pScratchPool = svn_pool_create( m_pPool );
	svn_error_t* pError;

	//
	// Defining file to get history from
	//
	apr_array_header_t* targets = apr_array_make( pScratchPool, 1, sizeof(const char*) );
	const char* strPath = apr_pstrdup( pScratchPool, rNodeIdentifier.m_strPath );
	APR_ARRAY_PUSH( targets, const char* ) = strPath;

	//
	// The peg_revision is the revision of the URL_OR_PATH (it is needed, since this file can be
	// renamed in another revision!) Use svn_opt_revision_unspecified here, to have "working" for
	// paths and "head" for URLs.
	//
	svn_opt_revision_t peg_revision;
	if( rNodeIdentifier.m_bHead )
		peg_revision.kind = svn_opt_revision_unspecified;
	else
	{
		peg_revision.kind = svn_opt_revision_number;
		peg_revision.value.number = rNodeIdentifier.m_iRevNumber;
	}

	//
	// Defining a revision range from HEAD to rev0
	//
	apr_array_header_t* revision_ranges = apr_array_make( pScratchPool, 1, sizeof(svn_opt_revision_range_t*) );
	svn_opt_revision_range_t* revision_range = (svn_opt_revision_range_t*)apr_pcalloc( pScratchPool, sizeof (svn_opt_revision_range_t) );
	revision_range->start.kind = svn_opt_revision_head;
	revision_range->start.value.number = 0;
	revision_range->end.kind = svn_opt_revision_number;
	revision_range->end.value.number = 0;
	APR_ARRAY_PUSH (revision_ranges, const svn_opt_revision_range_t*) = revision_range;


	// Get all logs (not only the first 'limit' ones)
	int limit = 0;

	// If discover_changed_paths is set, then the `changed_paths' argument to receiver will be
	// passed on each invocation.
	svn_boolean_t discover_changed_paths = true;

	// If strict_node_history is set, copy history (if any exists) will not be traversed while
	// harvesting revision logs for each target.
	svn_boolean_t strict_node_history = false;

	// If include_merged_revisions is set, log information for revisions which have been merged to
	// targets will also be returned.
	svn_boolean_t include_merged_revisions = true;

	// If revprops is NULL, retrieve all revprops; else, retrieve only the revprops named in the
	// array (i.e. retrieve none if the array is empty).
	const apr_array_header_t *revprops = NULL;

	svn_log_entry_receiver_t receiver = TransferHistoryEntry;
	void *receiver_baton = (void*) pListener;


	//
	// Call CLIENT_METHOD here
	//

	m_pContext->progress_func = TransferProgressInfo;
	SvnProgressContainer* pProgressContainer = (SvnProgressContainer*)apr_pcalloc(pScratchPool,
	                                                                  sizeof(SvnProgressContainer));
	pProgressContainer->m_pListener = pListener;
	pProgressContainer->m_oTime = QTime();
	pProgressContainer->m_oTime.start();
	m_pContext->progress_baton = (void*)pProgressContainer;

	pError = svn_client_log5( targets, &peg_revision, revision_ranges, limit, discover_changed_paths,
	                          strict_node_history, include_merged_revisions, revprops, receiver,
	                          receiver_baton, m_pContext, pScratchPool );
	pListener->OnHistoryStatistics( "svn log", pProgressContainer->m_oTime.elapsed(),
	                                pProgressContainer->m_iTransfered );


	//
	// Check for errors
	//
	if( pError != NULL )
	{
		qWarning() << "Error in svn_client_list2:" << pError->message << pError->file << pError->line;
		svn_error_clear( pError );
		svn_pool_destroy(pScratchPool);
		return false;
	}
	svn_pool_destroy(pScratchPool);

	return true;

}	// END GetCompleteHistory( const SvnRevisionIdentifier&, SvnHistoryListener* )

/** <!--------------------------------------------------------------------------------------------->
 * Returns a URL from the given path. (The URL can also be invalid!)
 *
 * @param rPath (in) Path to convert to URL
 * @returns a URL from the given path
 <!---------------------------------------------------------------------------------------------> */
QUrl SvnConnection::CreateUrl( const QString& rPath )
{
	const char* strUrl;
	apr_pool_t *pScratchPool = svn_pool_create( m_pPool );

	//
	// Convert path to URL (by svn_client lib)
	//
	svn_error_t* pError = svn_client_url_from_path( &strUrl, rPath.toAscii(), pScratchPool );

	//
	// Error Handling
	//
	if( pError )
	{
		qWarning() << "Error in svn_client_url_from_path:" << pError->message;

		svn_error_clear( pError );
		svn_pool_destroy( pScratchPool );

		return QUrl();
	}

	//
	// Copy value to QString
	//
	QString strRetVal( strUrl );

	svn_pool_destroy( pScratchPool );
	return QUrl( strRetVal );

}	// CreateUrl( const QString& )

/** <!--------------------------------------------------------------------------------------------->
 * Returns a root URL from the given path. (The URL can also be invalid!)
 *
 * @param rPath (in) Path to get root-url for
 * @returns the root-URL of the given path
 <!---------------------------------------------------------------------------------------------> */
QUrl SvnConnection::CreateRootUrl( const QString& rPath )
{
	const char* strUrl;
	apr_pool_t *pScratchPool = svn_pool_create( m_pPool );

	//
	// Convert path to URL (by svn_client lib)
	//
	svn_error_t* pError = svn_client_root_url_from_path( &strUrl, rPath.toAscii(), m_pContext,
	                                                     pScratchPool );

	//
	// Error Handling
	//
	if( pError )
	{
		qWarning() << "Error in svn_client_root_url_from_path:" << pError->message;

		svn_error_clear( pError );
		svn_pool_destroy( pScratchPool );

		return QUrl();
	}

	//
	// Copy value to QString
	//
	QString strRetVal( strUrl );

	svn_pool_destroy( pScratchPool );
	return QUrl( strRetVal );

}	// END CreateRootUrl( const QString& )

/*

void SvnConnection::GetMergeInfo()
{

	svn_mergeinfo_t mergeinfo;
	const char *path_or_url = URL_OR_PATH;
//	const char *path_or_url = "svn://localhost/testproject/trunk";
//	const char *path_or_url = "svn://localhost/testproject/branches/release-1.x";
//	const char *path_or_url = "svn://localhost/testproject/branches/work_route";
//	const char *path_or_url = "https://gsvn.googlecode.com/svn/branches/";
	svn_opt_revision_t peg_revision;
	peg_revision.kind = svn_opt_revision_unspecified;
//	peg_revision.kind = svn_opt_revision_number;
//	peg_revision.value.number = 13;

	pError = svn_client_mergeinfo_get_merged( &mergeinfo, path_or_url, &peg_revision, ctx, pool );


	if( pError != NULL )
	{
		qWarning() << "Error in svn_client_mergeinfo_get_merged:" << pError->message << pError->file << pError->line;
		svn_error_clear( pError );
		return svn_pool_destroy(pool);
	}

	if( mergeinfo == NULL )
	{
		qDebug() << "mergeinfo is NULL!";
	}
	else
	{

		for( apr_hash_index_t* hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi) )
		{
			const char* path;
			apr_array_header_t* rangeList;
			apr_hash_this(hi, (const void**)&path, NULL, (void**)&rangeList);

			qDebug() << "Merges for " << path << ":";
			for( int iCnt=0; iCnt < rangeList->nelts; iCnt++ )
			{
				svn_merge_range_t* hugo = APR_ARRAY_IDX( rangeList, iCnt, svn_merge_range_t*);

				qDebug() << "  -> start=" << hugo->start << " stop=" << hugo->end << " inh=" << hugo->inheritable;
			}
		}
	}
}

*/


/** <!--------------------------------------------------------------------------------------------->
 * Call back method to ask the user for login data
 *
 * @param pCredentials (out) Credentials to be written
 * @param pBaton       (in)  Baton (in this case a pointer to SvnConnection object)
 * @param strRealm     (in)  If realm is non-NULL, maybe use it in the prompt string.
 * @param strUsername  (in)  Predefined username (if non-null, promt only for password)
 * @param bMaySave     (in)  System daos not allow to save the credentials to disk
 * @param pPool        (in)  Pool for memory management
 <!---------------------------------------------------------------------------------------------> */
svn_error_t* SvnConnection::PromtUserForLoginData( svn_auth_cred_simple_t** pCredentials,
                                                   void* pBaton, const char* strRealm,
                                                   const char* strUsername, svn_boolean_t bMaySave,
                                                   apr_pool_t* pPool )
{
	// Since it's a static method, we use the pCancelBaton instead of $this
	const SvnConnection* pThis = static_cast<SvnConnection*>( pBaton );

	Q_UNUSED( bMaySave );
	Q_UNUSED( pThis );

	//
	// Create credentials object from pool
	//
	svn_auth_cred_simple_t* pRetVal = (svn_auth_cred_simple_t*)apr_pcalloc(pPool, sizeof(*pRetVal));

	if( strRealm != NULL )
		qDebug() << "Authentication realm is " << strRealm;

	if( strUsername != NULL )
	{
		qDebug() << "Pre-given username is " << strUsername;
		pRetVal->username = apr_pstrdup( pPool, strUsername );
	}
	else
	{
		qWarning() << "FIXME: Asking the user to set a REAL username. Using 'no-username' instead.";
		pRetVal->username = apr_pstrdup( pPool, "no-username" );
	}

	qWarning() << "FIXME: Asking the user to set a REAL password. No using 'no-password' instead.";
	pRetVal->password = apr_pstrdup( pPool, "no-password" );

	//
	// On success, copy results to call-by-reference variable
	//
	*pCredentials = pRetVal;
	return SVN_NO_ERROR;

}	// END PromtUserForLoginData

/** <!--------------------------------------------------------------------------------------------->
 * Call back method to ask the user for username
 *
 * @param pCredentials (out) Credentials to be written
 * @param pBaton       (in)  Baton (in this case a pointer to SvnConnection object)
 * @param strRealm     (in)  If realm is non-NULL, maybe use it in the prompt string.
 * @param bMaySave     (in)  System daos not allow to save the credentials to disk
 * @param pPool        (in)  Pool for memory management
 <!---------------------------------------------------------------------------------------------> */
svn_error_t* SvnConnection::PromtUserForUsername( svn_auth_cred_username_t** pCredentials,
                                                  void* pBaton, const char* strRealm,
                                                  svn_boolean_t bMaySave, apr_pool_t* pPool )
{
	// Since it's a static method, we use the pCancelBaton instead of $this
	const SvnConnection* pThis = static_cast<SvnConnection*>( pBaton );

	Q_UNUSED( bMaySave );
	Q_UNUSED( pThis );

	//
	// Create credentials object from pool
	//
	svn_auth_cred_username_t* pRetVal = (svn_auth_cred_username_t*)apr_pcalloc(pPool, sizeof(*pRetVal));

	if( strRealm != NULL )
		qDebug() << "Authentication realm is " << strRealm;

	qWarning() << "FIXME: Asking the user to set a REAL username. No using 'no-username' instead.";
	pRetVal->username = apr_pstrdup( pPool, "no-username" );

	//
	// On success, copy results to call-by-reference variable
	//
	*pCredentials = pRetVal;
	return SVN_NO_ERROR;

}	// END PromtUserForLoginData

/** <!--------------------------------------------------------------------------------------------->
 * A user defined callback that subversion will call with a user defined baton to see if the
 * current operation should be continued.
 *
 * @param pCancelBaton (in)  Baton (in this case a pointer to SvnConnection object)
 <!---------------------------------------------------------------------------------------------> */
svn_error_t* SvnConnection::CheckForCancelCallback( void* pCancelBaton )
{
	// Since it's a static method, we use the pCancelBaton instead of $this
	const SvnConnection* pThis = static_cast<SvnConnection*>( pCancelBaton );

	// If the operation should continue, the function should return @c SVN_NO_ERROR,
	// if not, it should return @c SVN_ERR_CANCELLED.
	if( pThis->m_bCanceled ) return svn_error_create( SVN_ERR_CANCELLED, NULL, "m_bCanceled" );
	else                     return SVN_NO_ERROR;

}	// END CheckForCancelCallback( void* pCancelBaton )



/** <!--------------------------------------------------------------------------------------------->
 * Call back method to transfer a history entry
 *
 * @param iProgress (in) Bytes which where transfered of this chunk
 * @param iTotal    (in) Total bytes of this chunk
 * @param pBaton    (in) Baton (in this case a pointer to SvnProgressContainer object)
 * @param pPool     (in) Pool for memory management
 <!---------------------------------------------------------------------------------------------> */
void SvnConnection::TransferProgressInfo( apr_off_t iProgress, apr_off_t iTotal, void* pBaton,
                                          apr_pool_t* pPool )
{
	// iTotal is not used, since it's often "-1"
	Q_UNUSED( iTotal );
	Q_UNUSED( pPool );

	SvnProgressContainer* pContainer = static_cast<SvnProgressContainer*>(pBaton);

	if( pContainer )
	{
		pContainer->m_iTransfered += iProgress;
		int iElapsed = pContainer->m_oTime.elapsed();
		float fThroughput = 0;
		if( iElapsed > 0 )
		{
			// through put in kB (bytes / milli sconds)
			fThroughput = pContainer->m_iTransfered / (float)iElapsed;
		}
		if( pContainer->m_pListener )
			pContainer->m_pListener->OnHistoryProgress( pContainer->m_iTransfered, fThroughput );
	}
}

/** <!--------------------------------------------------------------------------------------------->
 * Call back method to transfer a history entry
 *
 * @param pBaton       (in) Baton (in this case a pointer to SvnHistoryListener object)
 * @param pLogEntry    (in) The log entry object to get the data from
 * @param pPool        (in) Pool for memory management
 <!---------------------------------------------------------------------------------------------> */
svn_error_t* SvnConnection::TransferHistoryEntry( void* pBaton, svn_log_entry_t* pLogEntry,
                                                  apr_pool_t* pPool )
{
	apr_pool_t* pScratchPool = svn_pool_create( pPool );

	SvnHistoryListener* pListener = static_cast<SvnHistoryListener*>( pBaton );

	if( pListener && pLogEntry )
	{
		if( pLogEntry->revision == SVN_INVALID_REVNUM )
		{
#ifdef DEBUG_SVN
			qDebug() << "END OF CHILDREN!";
#endif // END DEBUG_SVN
			svn_pool_destroy(pScratchPool);
			return SVN_NO_ERROR;
		}
#ifdef DEBUG_SVN
		if( pLogEntry->has_children )
		{
			qDebug() << pLogEntry->revision << "HAS CHILDREN!";
		}
#endif // END DEBUG_SVN

		QString strMessage = "no-log-present";
		QString strAuthor  = "no-author-present";
		QDateTime   oDateTime;
		QStringList aChangedPaths;
		QList<SvnHistoryListener::SvnCopyInfo> aCopyInfo;

		if( pLogEntry->revprops )
		{
//			apr_hash_index_t* pIter;
//			for( pIter = apr_hash_first( pScratchPool, pLogEntry->revprops); pIter; pIter = apr_hash_next(pIter) )
//			{
//				const char* strKey;
//				apr_ssize_t iLength;
//				apr_hash_this( pIter, (const void**)&strKey, &iLength, NULL );
//				qDebug() << "  " << strKey << "with length" << iLength;
//			}

			svn_string_t* pMessage = (svn_string_t*)apr_hash_get( pLogEntry->revprops, "svn:log",
			                                                      APR_HASH_KEY_STRING );
			svn_string_t* pAuthor  = (svn_string_t*)apr_hash_get( pLogEntry->revprops, "svn:author",
			                                                      APR_HASH_KEY_STRING );
			svn_string_t* pDate    = (svn_string_t*)apr_hash_get( pLogEntry->revprops, "svn:date",
			                                                      APR_HASH_KEY_STRING );
			if( pMessage != NULL )
				strMessage = QString( pMessage->data );
			if( pAuthor != NULL )
				strAuthor = QString( pAuthor->data );
			if( pDate != NULL )
				oDateTime = QDateTime::fromString( pDate->data, Qt::ISODate );
		}

		if (pLogEntry->changed_paths2)
		{
			apr_hash_index_t* pElem;
			for( pElem = apr_hash_first(pPool, pLogEntry->changed_paths2); pElem;
			     pElem = apr_hash_next(pElem) )
			{
				const char* strKey;
				svn_log_changed_path2_t* pPathItem;

				apr_hash_this(pElem, (const void**)&strKey, NULL, (void**)&pPathItem);

				aChangedPaths << strKey;

				if( pPathItem )
				{
					if( pPathItem->copyfrom_path && SVN_IS_VALID_REVNUM(pPathItem->copyfrom_rev))
					{
						foreach( const SvnHistoryListener::SvnCopyInfo& rCopyInfo, aCopyInfo )
						{
							if( rCopyInfo.m_iFromRev == pPathItem->copyfrom_rev &&
							    rCopyInfo.m_strFromPath == pPathItem->copyfrom_path )
							{
								break;
							}
						}

						aCopyInfo << SvnHistoryListener::SvnCopyInfo( pPathItem->copyfrom_path,
						                                              pPathItem->copyfrom_rev );
					}
				}
#ifdef DEBUG_SVN
				QString strLine = QString(" %1  %2  %3").arg(pLogEntry->revision)
				                   .arg(pPathItem->action).arg(strKey);

				if( pPathItem )
				{
					if( pPathItem->copyfrom_path && SVN_IS_VALID_REVNUM(pPathItem->copyfrom_rev))
					{
						strLine += QString("  from %1:%2").arg(pPathItem->copyfrom_path)
														  .arg(pPathItem->copyfrom_rev);
					}
					switch( pPathItem->node_kind )
					{
						case svn_node_none:
							strLine += "  (absent)";
							break;
						case svn_node_file:
							strLine += "  (regular file)";
							break;
						case svn_node_dir:
							strLine += "  (directory)";
							break;
						case svn_node_unknown:
							strLine += "  -- something's here, but we don't know what";
							break;
					}
				}
				qDebug() << strLine.toAscii();
#endif // END DEBUG_SVN
			}
		}

		pListener->OnHistoryReceive( pLogEntry->revision, oDateTime, strMessage, strAuthor,
		                             aChangedPaths, aCopyInfo );
	}

	svn_pool_destroy(pScratchPool);
	return SVN_NO_ERROR;

}	// END TransferHistoryEntry

